package com.lix.controller.workflow;

import com.lix.auth.filter.TokenVerificationFilter;
import com.lix.base.BaseResult;
import com.lix.controller.workflow.cmd.DelelteExecutionCommand;
import com.lix.controller.workflow.cmd.DeleteTaskCommand;
import com.lix.enums.BusinessStatusEnum;
import com.lix.service.workflow.IBusinessStatusService;
import com.lix.utils.DateUtils;
import com.lix.utils.UserUtils;
import com.lix.vo.workflow.req.TaskCompleteReq;
import com.lix.vo.workflow.req.TaskReq;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.activiti.api.task.model.builders.TaskPayloadBuilder;
import org.activiti.api.task.runtime.TaskRuntime;
import org.activiti.bpmn.model.*;
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.history.HistoricTaskInstanceQuery;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.engine.task.TaskQuery;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 个人任务管理
 */
@Slf4j
@Api(tags = "任务管理控制层")
@RestController
@RequestMapping("/flow/task")
public class TaskController {

    @Autowired
    private TaskService taskService;

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private TaskRuntime taskRuntime;

    @Autowired
    private HistoryService historyService;

    @Autowired
    private IBusinessStatusService businessStatusService;

    @Autowired
    private ManagementService managementService;

    @Autowired
    private UserUtils userUtils;

    @ApiOperation("查询当前用户的待办任务")
    @PostMapping("/list/wait")
    public BaseResult findWaitTask(@RequestBody TaskReq req) {

        // 办理人（当前用户）
        String assignee = userUtils.getUsername();
        try {
            TaskQuery query = taskService.createTaskQuery()
                    .taskCandidateOrAssigned(assignee) // 候选人或者办理人
                    .orderByTaskCreateTime().asc();

            if (StringUtils.isNotEmpty(req.getTaskName())) {
                query.taskNameLikeIgnoreCase("%" + req.getTaskName() + "%");
            }

            // 分页查询
            List<Task> taskList = query.listPage(req.getFirstResult(), req.getPageSize());

            // 用于前端显示页面，总记录数
            long total = query.count();

            List<Map<String, Object>> records = new ArrayList<>();
            for (Task task : taskList) {
                Map<String, Object> result = new HashMap<>();
                // 任务id
                result.put("taskId", task.getId());
                // 任务名称
                result.put("taskName", task.getName());
                // 状态
                result.put("processStatus", task.isSuspended() ? "已暂停" : "已启动");
                // 任务的创建时间
                result.put("taskCreateTime", DateUtils.toStringDate(task.getCreateTime()));
                // 流程实例ID
                result.put("processInstanceId", task.getProcessInstanceId());
                // 执行对象ID
                result.put("executionId", task.getExecutionId());
                // 流程定义ID
                result.put("processDefinitionId", task.getProcessDefinitionId());
                // 任务办理人: 如果是候选人则没有值，办理人才有，候选人要先签收
                result.put("taskAssignee", task.getAssignee());

                // 查询流程实例
                ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
                // 流程名称
                result.put("processName", pi.getProcessDefinitionName());
                // 版本号
                result.put("version", pi.getProcessDefinitionVersion());
                // 获取发起人
                result.put("proposer", pi.getStartUserId());
                // 业务唯一标识
                result.put("businessKey", pi.getBusinessKey());

                records.add(result);
            }

            Map<String, Object> result = new HashMap<>();
            result.put("total", total);
            result.put("records", records);
            return BaseResult.ok(result);
        } catch (Exception e) {
            log.error("根据流程assignee查询当前人的个人任务,异常:{}", e);
            return BaseResult.error("查询失败" + e.getMessage());
        }
    }

    /**
     * 获取目标节点(人工任务)
     *
     * @param taskId
     * @return
     */
    @ApiOperation("获取目标节点（下一个节点）")
    @GetMapping("/next/node")
    public BaseResult getNextNodeInfo(@RequestParam String taskId) {
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
        if (task == null) {
            return BaseResult.error("任务不存在");
        }

        // 2. 从当前任务信息中获取此流程定义id，
        String processDefinitionId = task.getProcessDefinitionId();
        // 3. 拿到流程定义id后可获取此bpmnModel对象
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);

        // 4. 通过任务节点id，来获取当前节点信息
        FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());
        // 封装下一个用户任务节点信息
        List<Map<String, Object>> nextNodes = new ArrayList<>();
        getNextNodes(flowElement, nextNodes);

        return BaseResult.ok(nextNodes);
    }

    /**
     * 判断当前节点的下一节点是人工任务的集合
     *
     * @param flowElement 当前节点
     * @param nextNodes   下节点名称集合
     * @return
     */
    public void getNextNodes(FlowElement flowElement, List<Map<String, Object>> nextNodes) {
        // 获取当前节点的连线信息
        List<SequenceFlow> outgoingFlows = ((FlowNode) flowElement).getOutgoingFlows();
        // 当前节点的所有下一节点出口
        for (SequenceFlow outgoingFlow : outgoingFlows) {
            // 下一节点的目标元素
            FlowElement nextFlowElement = outgoingFlow.getTargetFlowElement();
            if (nextFlowElement instanceof UserTask) {
                // 用户任务，则获取响应给前端设置办理人或者候选人
                Map<String, Object> node = new HashMap<>();
                node.put("id", nextFlowElement.getId()); // 节点id
                node.put("name", nextFlowElement.getName()); // 节点id
                nextNodes.add(node);
            } else if (nextFlowElement instanceof EndEvent) {
                break;
            } else if (nextFlowElement instanceof ParallelGateway // 并行网关
                    || nextFlowElement instanceof ExclusiveGateway) { // 排他网关
                getNextNodes(nextFlowElement, nextNodes);
            }
        }
    }

    @ApiOperation("完成任务")
    @PostMapping("/complete")
    public BaseResult completeTask(@RequestBody TaskCompleteReq req) {

        String taskId = req.getTaskId();
        //1. 查询任务信息
        org.activiti.api.task.model.Task task = taskRuntime.task(taskId);
        if (task == null) {
            return BaseResult.error("任务不存在或不是您办理的任务");
        }
        String procInstId = task.getProcessInstanceId();

        // 2. 指定任务审批意见
        taskService.addComment(taskId, procInstId, req.getMessage());

        // 3. 完成任务
        taskRuntime.complete(TaskPayloadBuilder.complete().withTaskId(taskId).build());

        // 4. 查询下一个任务
        List<Task> taskList = taskService.createTaskQuery().processInstanceId(procInstId).list();

        // 5. 指定办理人
        if (CollectionUtils.isEmpty(taskList)) {
            /**
             * 1. 获取流程实例拿到业务id,
             * 注意新api的Task对象有businessKey属性，但是没有值(task.getBusinessKey() m5版本中没有 值)
             * 并且是整个流程结束，所以查询历史实例获取businessKey
             */
            HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery().processInstanceId(procInstId).singleResult();
            // 更新业务状态已完成
            return businessStatusService.updateState(hpi.getBusinessKey(), BusinessStatusEnum.FINISH);
        } else {
            // 有下一个人工任务
            Map<String, String> assigneeMap = req.getAssigneeMap();
            if (assigneeMap == null) {
                // 如果没有办理人，直接将流程实例删除（非法操作）
                return deleteProcessInstance(procInstId);
            }
            // 有办理人，针对每个任务分配审批人
            for (Task t : taskList) {
                if (StringUtils.isNotEmpty(t.getAssignee())) {
                    // 如果当前任务有办理人，则直接忽略，不用指定办理人
                    continue;
                }
                // 根据当前任务节点id获取对应的办理人
                String[] assignees = req.getAssignees(t.getTaskDefinitionKey());
                if (ArrayUtils.isEmpty(assignees)) {
                    // 如果下个节点未分配审批人，为空（没有办理人）-- 结束删除流程实例
                    return deleteProcessInstance(procInstId);
                }

                // 分配第一个任务办理人
                if (assignees.length == 1) {
                    taskService.setAssignee(t.getId(), assignees[0]);
                } else {
                    // 多个办理人的情况下，就作为候选人
                    for (String assignee : assignees) {
                        taskService.addCandidateUser(t.getId(), assignee);
                    }
                }
            }
        }

        return BaseResult.ok();
    }

    /**
     * 结束流程：删除流程实例，精力状态更新为取消状态
     *
     * @param procInstId 流程实例id
     * @return
     */
    private BaseResult deleteProcessInstance(String procInstId) {
        // 1.删除流程实例
        runtimeService.deleteProcessInstance(procInstId, "审批节点未分配审批人，流程直接中断取消");
        // 2.通过任务对象获取流程实例
        HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery().processInstanceId(procInstId).singleResult();
        // 3.更新业务状态
        businessStatusService.updateState(hpi.getBusinessKey(), BusinessStatusEnum.CANCEL);
        return BaseResult.ok("审批节点未分配审批人，流程直接自动中断取消");
    }

    @ApiOperation("签收（拾取）任务")
    @PostMapping("/claim")
    public BaseResult claimTask(@RequestParam String taskId) {
        try {
            taskRuntime.claim(TaskPayloadBuilder.claim().withTaskId(taskId).build());
            return BaseResult.ok();
        } catch (Exception e) {
            e.printStackTrace();
            return BaseResult.error("签收任务失败：" + e.getMessage());
        }
    }

    @ApiOperation("任务转办，把任务交给别人处理")
    @PostMapping("/turn")
    public BaseResult turnTask(@RequestParam String taskId, @RequestParam String assigneeUserKey) {
        try {
            org.activiti.api.task.model.Task task = taskRuntime.task(taskId);
            // 转办
            taskService.setAssignee(taskId, assigneeUserKey);
            // 处理意见
            String message = String.format("%s 转办任务【%s】给 %s 办理", userUtils.getUsername(), task.getName(), assigneeUserKey);
            taskService.addComment(taskId, task.getProcessInstanceId(), message);
            return BaseResult.ok(taskId);
        } catch (Exception e) {
            log.error("任务转办,异常:{}", e);
            return BaseResult.error("转办任务失败:" + e.getMessage());
        }
    }

    @ApiOperation("获取历史任务节点，用于驳回功能")
    @GetMapping("/back/nodes")
    public BaseResult getBackNodes(@RequestParam String taskId) {
        try {
            Task task = taskService.createTaskQuery()
                    .taskId(taskId)
                    .taskAssignee(userUtils.getUsername())
                    .singleResult();
            if (task == null) {
                return BaseResult.error("没有此任务或不是该任务办理人");
            }
            // 查询历史任务节点
           /* List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery()
                    .processInstanceId(task.getProcessInstanceId())
                    .list();*/
            // 查询历史任务节点，且去重
            // 注意：必须带任务id_值，不然查询出来的全部相同数据。而且原id每条数据都是不相同，没法去重，因此使用rand()随机数作为id
            // 不把当前节点查询出来, 没有办理完的节点不查询，每条数据都有一个唯一值，我们使用随机数
            String sql = "select rand() AS ID_, t2.* from " +
                    " ( select distinct t1.TASK_DEF_KEY_, t1.NAME_ from " +
                    "  ( select ID_, RES.TASK_DEF_KEY_, RES.NAME_, RES.START_TIME_, RES.END_TIME_ " +
                    "   from ACT_HI_TASKINST RES " +
                    "   WHERE RES.PROC_INST_ID_ = #{processInstanceId} and TASK_DEF_KEY_ != #{taskDefKey}" +
                    "   and RES.END_TIME_ is not null order by RES.START_TIME_ asc " +
                    "  ) t1 " +
                    " ) t2";

            List<HistoricTaskInstance> list = historyService.createNativeHistoricTaskInstanceQuery()
                    .sql(sql)
                    .parameter("processInstanceId", task.getProcessInstanceId())
                    .parameter("taskDefKey", task.getTaskDefinitionKey()) // 不把当前节点查询出来
                    .list();

            List<Map<String, Object>> records = new ArrayList<>();

            for (HistoricTaskInstance hti : list) {
                Map<String, Object> data = new HashMap<>();
                data.put("activityId", hti.getTaskDefinitionKey()); // 节点id
                data.put("activityName", hti.getName());    // 节点名称
                records.add(data);
            }

            return BaseResult.ok(records);
        } catch (Exception e) {
            log.error("查询获取历史任务节点，用于驳回功能,异常:{}", e);
            e.printStackTrace();
            return BaseResult.error("查询驳回节点失败：" + e.getMessage());
        }
    }

    /**
     * 实现驳回到指定节点（ 实现并行网关驳回）
     * 1.取得当前节点信息
     * 2.获取驳回的目标节点信息
     * 要考虑并行网关：从选中的目标节点的上级节点（如：并行网关），找到其上级节点的所有子节点，并行网关就会有多条子节点
     * 3.将当前节点出口指定为驳回的目标节点，（并行网关是多条）
     * 4.完成当前节点任务，删除执行表 is_active_=0数据，不然并行汇聚不向后流转；删除其他并行任务，
     * 5.分配目标节点原办理人。
     */
    @ApiOperation("驳回历史节点")
    @PostMapping("/back")
    public BaseResult backProcess(@RequestParam String taskId, @RequestParam String targetActivityId) throws Exception {
        try {
            // 1.查询当前任务信息
            Task task = taskService.createTaskQuery()
                    .taskId(taskId)
                    .taskAssignee(userUtils.getUsername())
                    .singleResult();
            // 判断当前用户是否为该节点处理人
            if (task == null) {
                return BaseResult.error("当前任务不存在或你不是任务办理人");
            }

            String procInstId = task.getProcessInstanceId();

            // 2.获取流程模型实例 BpmnModel
            BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
            // 3.获取当前节点信息(强类型转换 FlowNode 获取连线）
            FlowNode curFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(task.getTaskDefinitionKey());
            // 4.获取当前节点的原出口连线
            List<SequenceFlow> sequenceFlowList = curFlowNode.getOutgoingFlows();
            // 5.临时存储当前节点的原出口连线（用于驳回后还原回来）
            List<SequenceFlow> oriSequenceFlows = new ArrayList<>();
            oriSequenceFlows.addAll(sequenceFlowList);
            // 6.将当前节点的原出口清空
            sequenceFlowList.clear();

            // 7.获取驳回的目标节点信息
            FlowNode targetFlowNode = (FlowNode) bpmnModel.getFlowElement(targetActivityId);
            // 8.获取驳回的新流向节点
            /**
             * 通过获取目标节点的入口连线，通过入口连线获取目标节点的父节点，看父节点有几个出口连线，
             * 如果父节点是并行或包含网关，出口连线就会有多条，否则一般是一条出口连线，此时出口连线的所有节点才是驳回的真实节点，
             *
             * 目的：解决并行网关多个出口情况，不使用驳回父节点找子节点，当驳回到并行网关下的任务节点时会汇聚不了直接结束。
             */
            // 获取目标节点的入口连线
            List<SequenceFlow> incomingFlows = targetFlowNode.getIncomingFlows();
            // 存储所有目标出口（存储获取驳回的新的流向）
            List<SequenceFlow> allSequenceFlow = new ArrayList<>();
            for (SequenceFlow incomingFlow : incomingFlows) {
                // 找到入口连线的源头（获取目标节点的父节点）
                FlowNode source = (FlowNode) incomingFlow.getSourceFlowElement();
                // 获取目标节点的父组件的所有出口
                List<SequenceFlow> sequenceFlows;
                if (source instanceof ParallelGateway) {
                    // 并行网关: 获取目标节点的父节点（并行网关）的所有出口，
                    sequenceFlows = source.getOutgoingFlows();
                } else {
                    // 其他类型父节点, 将目标入口作为当前节点的出口（哪来的回哪去）
                    sequenceFlows = targetFlowNode.getIncomingFlows();
                }
                // 找到后把它添加到集合作为新方向
                allSequenceFlow.addAll(sequenceFlows);
            }

            // 9. 将当前节点的出口设置为新节点
            curFlowNode.setOutgoingFlows(allSequenceFlow);

            // 10. 完成当前任务，流程就会流向目标节点创建新目标任务
            // 查询当前流程实例的所有任务，进行完成和删除任务，删除执行数据表act_ru_execution中is_active_=0的数据
            List<Task> list = taskService.createTaskQuery().processInstanceId(procInstId).list();
            for (Task t : list) {
                // 如果是当前节点任务（并行网关后面多个任务），完成任务让流程流向新的驳回节点
                if (taskId.equals(t.getId())) {
                    // 当前任务，完成当前任务，保存记录 『 xxx 驳回任务 【行政审批】→【部门领导审批】 』
                    String message = String.format("【%s 驳回任务 %s => %s】", userUtils.getUsername(), task.getName(), targetFlowNode.getName());
                    taskService.addComment(t.getId(), procInstId, message);
                    // 完成任务(驳回完成)，就会进行驳回到目标节点，产生目标节点的任务数据
                    taskService.complete(taskId);

                    /**
                     * 完成的这条任务在act_ru_execution表中对应的执行数据，is_active_ 会更新为0，
                     * 更新后，会影响并行任务节点操作驳回，然后又流向回来时并行任务完成一个就会往后走，不会等其他并行任务完成，
                     * 因为并行汇聚是通过判断并行任务的执行数据 is_active_=0 ，所以要把当前is_active_=0 的数据删除
                     */

                    // 删除执行表中 is_active_ = 0的执行数据， 使用command自定义模型
                    DelelteExecutionCommand deleteExecutionCMD = new DelelteExecutionCommand(task.getExecutionId());
                    managementService.executeCommand(deleteExecutionCMD);
                } else {
                    // 删除其他未完成的并行任务（非当前节点任务）
                    // taskService.deleteTask(taskId); // 注意这种方式删除不掉，会报错：流程正在运行中无法删除。
                    // 使用command自定义命令模型来删除，直接操作底层的删除表对应的方法，对应的自定义是否删除
                    DeleteTaskCommand deleteTaskCMD = new DeleteTaskCommand(t.getId(), userUtils.getUsername());
                    managementService.executeCommand(deleteTaskCMD);
                }
            }

            // 13. 完成驳回功能后，将当前节点的原出口方向进行恢复
            curFlowNode.setOutgoingFlows(oriSequenceFlows);

            // 12. 查询目标任务节点历史办理人，也就是查询驳回后的新任务
            List<Task> newTaskList = taskService.createTaskQuery().processInstanceId(procInstId).list();
            // 设置新任务执行人
            for (Task newTask : newTaskList) {
                // 取目标节点的历史办理人
                HistoricTaskInstance oldTargerTask = historyService.createHistoricTaskInstanceQuery()
                        .taskDefinitionKey(newTask.getTaskDefinitionKey()) // 同一节点id
                        .processInstanceId(procInstId)
                        .finished() // 已经完成才是历史
                        .orderByTaskCreateTime().desc() // 最新办理的在最前面
                        .list().get(0);
                taskService.setAssignee(newTask.getId(), oldTargerTask.getAssignee());
            }

            return BaseResult.ok();
        } catch (Exception e) {
            log.error("完成任务,异常:{}", e.getMessage());
            e.printStackTrace();
            return BaseResult.error("驳回失败：" + e.getMessage());
        }
    }

    /**
     * 查询个人已办理任务
     *
     * @param req
     * @return
     */
    @ApiOperation("查询当前登录用户已完成任务信息")
    @PostMapping("/list/complete")
    public BaseResult findCompleteTask(@RequestBody TaskReq req) {
        try {
            HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery()
                    .taskAssignee(userUtils.getUsername())
                    .orderByTaskCreateTime()
                    .desc()
                    .finished();    // 已办任务

            if (StringUtils.isNotEmpty(req.getTaskName())) {
                query.taskNameLike("%" + req.getTaskName() + "%");
            }

            // 分页查询
            List<HistoricTaskInstance> taskList = query.listPage(req.getFirstResult(), req.getPageSize());
            // 用于前端显示页面，总记录数
            long total = query.count();

            List<Map<String, Object>> records = new ArrayList<>();

            for (HistoricTaskInstance task : taskList) {
                Map<String, Object> result = new HashMap<>();
                result.put("taskId", task.getId());
                result.put("taskName", task.getName());
                result.put("taskStartTime", DateUtils.toStringDate(task.getStartTime()));
                result.put("taskEndTime", DateUtils.toStringDate(task.getEndTime()));
                result.put("processInstanceId", task.getProcessInstanceId());       // 流程实例id
                result.put("processDefinitionId", task.getProcessDefinitionId());   // 流程定义id

                // 查询流程实例
                HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery()
                        .processInstanceId(task.getProcessInstanceId()).singleResult();
                // 流程名称
                result.put("processName", pi.getProcessDefinitionName());
                result.put("version", pi.getProcessDefinitionVersion());
                // 获取发起人
                result.put("proposer", pi.getStartUserId());
                // 业务唯一标识
                result.put("businessKey", pi.getBusinessKey());

                records.add(result);
            }

            Map<String, Object> result = new HashMap<>();
            result.put("total", total);
            result.put("records", records);
            return BaseResult.ok(result);
        } catch (Exception e) {
            log.error("查询当前用户的已处理任务,异常:{}", e);
            e.printStackTrace();
            return BaseResult.error("查询失败：" + e.getMessage());
        }
    }

}
